﻿using System;
using System.Collections.Generic;

namespace Qadir.Collections.Generic
{
    /// <summary>
    /// Represents a three-dimensional collection of keys and values.
    /// </summary>
    /// <typeparam name="TKey">The type that will be used to index this Trictionary.</typeparam>
    /// <typeparam name="TPrimaryValue">The primary value type associated with this instance.</typeparam>
    /// <typeparam name="TSecondaryValue">The secondary value type associated with this instance.</typeparam>
    public class Trictionary<TKey, TPrimaryValue, TSecondaryValue>
    {
        /// <summary>
        /// All keys contained within this instance.
        /// </summary>
        public readonly Manager<TKey> Keys;
        
        /// <summary>
        /// All primary values contained within this instance.
        /// </summary>
        public readonly Manager<TPrimaryValue> PrimaryValues;

        /// <summary>
        /// All secondary values contained within this instance.
        /// </summary>
        public readonly Manager<TSecondaryValue> SecondaryValues;

        public Trictionary()
        {
            Keys = new Manager<TKey>();
            PrimaryValues = new Manager<TPrimaryValue>();
            SecondaryValues = new Manager<TSecondaryValue>();
        }

        /// <summary>
        /// Gets or sets the values associated with a given key.
        /// </summary>
        /// <param name="key">The key with which to index this Trictionary.</param>
        /// <returns>Returns a KeyValuePair containing the primary and secondary values associated with the given key.</returns>
        public KeyValuePair<TPrimaryValue,TSecondaryValue> this[TKey key]
        {
            get { return GetValues(key); }
            set 
            {
                if (ContainsKey(key))
                    Set(key, value);
                else
                    Add(key, value.Key, value.Value);
            }
        }

        /// <summary>
        /// Adds a triplet of key and values to the Trictionary.
        /// </summary>
        /// <param name="triplet">A triplet containing values to be added to this Trictionary.</param>
        public void Add(Triplet<TKey,TPrimaryValue,TSecondaryValue> triplet)
        {
            Keys.Add(triplet.Key);
            PrimaryValues.Add(triplet.PrimaryValue);
            SecondaryValues.Add(triplet.SecondaryValue);
        }

        /// <summary>
        /// Adds a triplet of key and values to the Trictionary.
        /// </summary>
        /// <param name="key">The key of the triplet.</param>
        /// <param name="primaryValue">The primary value of the triplet.</param>
        /// <param name="secondaryValue">The secondary value of the triplet.</param>
        public void Add(TKey key, TPrimaryValue primaryValue, TSecondaryValue secondaryValue)
        {
            Keys.Add(key);
            PrimaryValues.Add(primaryValue);
            SecondaryValues.Add(secondaryValue);
        }

        /// <summary>
        /// Determines whether the trictionary contains the specified key.
        /// </summary>
        /// <param name="key">The key to search for.</param>
        /// <returns>Returns true if the trictionary contains the key, or false if it does not.</returns>
        public bool ContainsKey(TKey key)
        {
            return Keys.Contains(key);
        }

        /// <summary>
        /// Obtains the primary and secondary values associated with a given key.
        /// </summary>
        /// <param name="key"></param>
        /// <returns>Returns a KeyValuePair containing the desired values, or throws an exception.</returns>
        /// <exception cref="Qadir.Collections.Generic.TrictionaryException">Throws a TrictionaryException if no matching key is found.</exception>
        public KeyValuePair<TPrimaryValue,TSecondaryValue> GetValues(TKey key)
        {
            if (ContainsKey(key))
            {
                int index = Keys.IndexOf(key);
                return new KeyValuePair<TPrimaryValue, TSecondaryValue>(PrimaryValues[index], SecondaryValues[index]);
            }
            else
                throw new TrictionaryException("No such key exists within this Trictionary.");
        }

        /// <summary>
        /// Sets the values associated with a given key.
        /// </summary>
        /// <param name="key">The key whose values are being set.</param>
        /// <param name="values">A KeyValuePair containing the values to set.</param>
        /// <exception cref="Qadir.Collections.Generic.TrictionaryException">Throws a TrictionaryException if no matching key is found.</exception>
        public void Set(TKey key, KeyValuePair<TPrimaryValue,TSecondaryValue> values)
        {
            if (ContainsKey(key))
            {
                int index = Keys.IndexOf(key);
                PrimaryValues[index] = values.Key;
                SecondaryValues[index] = values.Value;
            }
            else
                throw new TrictionaryException("No such key exists within this Trictionary.");
        }

        /// <summary>
        /// Sets the values associated with a given key.
        /// </summary>
        /// <param name="key">The key whose values are being set.</param>
        /// <param name="primaryValue">The new primary value to be associated with this key.</param>
        /// <param name="secondaryValue">The new secondary value to be associated with this key.</param>
        public void Set(TKey key, TPrimaryValue primaryValue, TSecondaryValue secondaryValue)
        {
            Set(key, new KeyValuePair<TPrimaryValue, TSecondaryValue>(primaryValue, secondaryValue));
        }
    }

    /// <summary>
    /// Represents a key and two values related to it.
    /// </summary>
    /// <typeparam name="TKey">The type that will be used to index this Triplet.</typeparam>
    /// <typeparam name="TPrimaryValue">The primary value type associated with this instance.</typeparam>
    /// <typeparam name="TSecondaryValue">The secondary value type associated with this instance.</typeparam>
    public class Triplet<TKey, TPrimaryValue, TSecondaryValue>
    {
        /// <summary>
        /// The key used as the main value of this instance.
        /// </summary>
        public TKey Key { get; set; }

        /// <summary>
        /// The primary value associated with this instance.
        /// </summary>
        public TPrimaryValue PrimaryValue { get; set; }

        /// <summary>
        /// The secondary value associated with this instance.
        /// </summary>
        public TSecondaryValue SecondaryValue { get; set; }

        /// <summary>
        /// Instantiates the Triplet.
        /// </summary>
        /// <param name="key">The key to be used as the main value of this instance.</param>
        /// <param name="primaryValue">The primary value to be associated with this instance.</param>
        /// <param name="secondaryValue">The secondary value to be associated with this instance.</param>
        public Triplet(TKey key, TPrimaryValue primaryValue, TSecondaryValue secondaryValue)
        {
            Key = key;
            PrimaryValue = primaryValue;
            SecondaryValue = secondaryValue;
        }
    }

    /// <summary>
    /// An exception that occurs when dealing with a Qadir.Collections.Generic.Trictionary.
    /// </summary>
    [Serializable]
    public class TrictionaryException : Exception
    {
        public TrictionaryException() { }
        public TrictionaryException(string message) : base(message) { }
        public TrictionaryException(string message, Exception inner) : base(message, inner) { }
        protected TrictionaryException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(info, context) { }
    }
}
